package us.versus.them.b3ge.character;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;

import us.versus.them.b3ge.Util;

class Actions extends Bitmap {
	var actions:Hash< Action >;
	var frame:Int;
	var current:Action;
	var maxDirection:Int;
	var maxFrame:Int;
	var xOffset:Int;
	var yOffset:Int;
	var direction_:Int;

	var d:Float;

	public function new( ?width=256, ?height=256) {
		super();
		this.bitmapData = new BitmapData( width, height, true, 0x0 );
		/* believe it or not, these two lines *MUST* come after this.bitmapData = ... */
		this.width = width;
		this.height = height;

		this.d = Math.sqrt( this.width * this.width + this.height * this.height );
		this.actions = new Hash< Action >();
		frame = xOffset = yOffset = maxFrame = maxDirection = 0;
		this.direction_ = 0;
	}

	public function add( name:String, data:BitmapData, ?offsets:Array< Point > = null ) {
		var action = new Action( data, offsets );
		this.actions.set( name, action );
		if ( null == this.current ) {
			this.action( name );
		}
	}

	public function closeEnough( diff:Point ) {
		return ( this.d >= Point.distance( Action.POINT_ZERO, diff ) );
	}

	// returns true if changing position will bring current closer to goal
	public function towards( current:Point, goal:Point ) {
		var diff = new Point( goal.x - current.x, goal.y - current.y );

		var closest = this.current.closest( diff );
		this.direction( closest );

		if ( null == this.offset ) {
			return true; 
		}
		
		var toGo = Point.distance( Action.POINT_ZERO, diff );
		var move = Point.distance( Action.POINT_ZERO, this.offset() );

		return toGo > move;
	}

	public function offset() {
		return if ( null == this.current || null == this.current.offset ) {
			Action.POINT_ZERO;
		} else {
			this.current.offset( this.direction_ );
		}
	}

	public function action( name:String ) {
		this.setCurrent( this.actions.get( name ) );
	}

	public function direction( direction:Int ) {
		direction = direction % maxDirection;
		if ( direction < 0 ) direction += maxDirection;
		this.direction_ = direction;
		this.yOffset = Std.int( direction * this.height );
	}

	public function update() {
		if ( null != this.current ) {
			this.bitmapData.copyPixels( 
				this.current.bitmapData
				, new Rectangle( this.xOffset, this.yOffset, this.width, this.height )
				, new Point( 0, 0 )
			);
		}
	}

	// just return the fractional part of the time 
	// representing current millis
	public function millis( perSecond:Float ) {
		return Date.now().getTime() % Std.int( perSecond * 1000 ) / ( perSecond * 1000.0 );
	}

	// returns true if the frame changed since the previous invocation
	// note: this only advaces the frame! it does not change the sprite's position
	public function move() {
		var previous = this.frame;

		// assumes: maxFrames will fill up 0.5 seconds of animation
		this.frame = Std.int( this.maxFrame * this.millis( 0.75 ) );

		this.xOffset = Std.int( this.frame * this.width );
		this.update();
		return this.frame == previous;
	}

	public function setCurrent( current:Action ) {
		this.current = current;
		var data = current.bitmapData;
		this.maxFrame = Std.int( data.width / this.width );
		this.maxDirection = Std.int( data.height / this.height );
		this.frame = this.frame % this.maxFrame;
		this.bindOffsets();
	}

	public function bindOffsets() {
		this.xOffset = this.bound( this.xOffset, 0, ( this.maxFrame - 1 ) * this.width );
		this.yOffset = this.bound( this.yOffset, 0, ( this.maxDirection - 1 ) * this.height );
	}

	public function bound( value:Float, min:Float, max:Float ) : Int{
		if ( value < min ) value = min;
		else if ( value > max ) value = max;
		return Std.int( value );
	}

	/**
	 *
	 * This is a little demo of how the code can
	 * be used. It is a sort of terrible way to do
	 * unit testing.
	 *
	 */
	public static function main() {
		var clip = flash.Lib.current;
		clip.addChild( Util.txt( 'click character changes angle' ) );
		var y = 20.0; // gotta love subpixel positioning!
		for ( character in [ "Johnnie", "Janie", "Skeleton" ] ) {
			demo( clip, character, y );
			y += 192;
		}
	}

	public static function demo( clip:flash.display.MovieClip, name, y ) {
		var direction = 0;

		var character = new Character();
		character.y = y;
		clip.addChild( character );

		for ( action in [ "Walk", "Die1", "Maul" ] ) {
			/* the joys of naming conventions! */
			var resource = "ResourcesCharactersSheets" + name + "" + action;
			try {
				character.actions.add( 
					action
					, Util.bitmapDataForName( resource ) 
					, [ 
						  new Point(   0, -128 ) // N
						, new Point(  90,  -90 ) // NE 
						, new Point( 128,    0 ) // E
						, new Point(  90,   90 ) // SE
						, new Point(   0,  128 ) // S
						, new Point( -90,   90 ) // SW
						, new Point( -128,   0 ) // W
						, new Point( -90,  -90 ) // NW
					]
				);
			} catch( e:Dynamic ) {
				trace( 'fail:' + resource );
				continue;
			}
			var txt = Util.buttonish( action );
			txt.x = character.width;
			y = txt.y = y + 22.0;
			txt.height = 18;
			clip.addChild( txt );
			txt.addEventListener(
				MouseEvent.CLICK
				, function( _ ) {
					character.actions.action( action );
				}
			);
		}
		character.addEventListener( 
			Event.ENTER_FRAME
			, function( _ ) { 
				character.actions.move(); 
			} 
		);
		character.addEventListener( 
			MouseEvent.CLICK
			, function( _ ) { 
				character.actions.direction( direction++ ); 
			} 
		);

		var characterLocation = new Point( character.x, character.y );

		clip.addEventListener(
			MouseEvent.MOUSE_MOVE
			, function( e:MouseEvent ) {
				character.actions.towards( 
					characterLocation
					, new Point( e.stageX, e.stageY )
				);
			}

		);
	}

}
